在昨天的文章中,我們透過生活中的例子理解了「同步」與「非同步」的核心差異。我們知道非同步能讓程式在等待的空檔去執行其他任務,大幅提升效率。但 Python 究竟是如何實現這件事的呢?答案就在於今天的主角:協程 (Coroutine) 與 事件迴圈 (Event Loop)。
協程,是 "Cooperative Routine" 的縮寫,可以理解為「可協作的常式」。它是一種特殊的函式,其特殊之處在於它可以在執行到某個點時暫停,將控制權交還出去,並在未來某個時刻從暫停的地方恢復執行。
在 Python 中,我們使用 async def
語法來定義一個協程函式:
import asyncio
async def my_coroutine():
print("協程開始執行")
await asyncio.sleep(1) # 在這裡暫停,交出控制權
print("協程恢復執行")
# 注意!直接呼叫協程函式並不會執行它
# 它只會回傳一個協程物件
coro_obj = my_coroutine()
print(coro_obj)
執行上面的程式碼,你會發現它只會印出一個協程物件的資訊,而不會印出 "協程開始執行"。這就像在餐廳點餐,你拿到的是一張等待叫號的號碼單(協程物件),而不是餐點本身。你需要一個服務生(事件迴圈)來為你處理這張訂單。
事件迴圈是 asyncio
應用程式的核心。你可以把它想像成一個任務管理者,它不斷地在迴圈中檢查:「現在有哪些任務可以執行?」
它的工作流程大致如下:
my_coroutine()
)註冊到事件迴圈中。await
關鍵字時(例如等待網路回應或 asyncio.sleep
),它會暫停該任務。這個不斷「執行-暫停-切換-恢復」的過程,就是非同步得以實現並行效果的根本原因。
import asyncio
async def main():
print("主程式開始")
# await 會告訴事件迴圈:執行 my_coroutine,並在這裡等待它完成
await my_coroutine()
print("主程式結束")
async def my_coroutine():
print("協程開始執行")
await asyncio.sleep(1)
print("協程恢復執行")
# asyncio.run(main()) 就是啟動事件迴圈,並將 main 協程作為第一個任務執行的指令
asyncio.run(main())
需要特別注意的是,這個例子中事件迴圈只處理了一個協程任務(my_coroutine
)。但在實際應用中,事件迴圈可以同時管理許多個協程任務,就像餐廳的服務生可以同時處理多個客人的訂單一樣。當某個任務在等待時(比如等待網路回應),事件迴圈會立刻切換到其他可執行的任務,這就是非同步程式設計真正發揮威力的地方。關於如何同時執行多個協程任務,我們明天在介紹 asyncio
時會有更完整的說明~
今天,我們介紹了非同步的幾個重要觀念:
async def
):是可以被暫停和恢復的函式,是我們非同步任務的基本單位。await
:是協程向事件迴圈發出的信號,表示「我要在這裡等待,你可以先去做別的事」。理解了這兩個核心組件後,我們明天將進一步探索 asyncio
這個 Python 內建的套件,看看它提供了哪些強大的工具來幫助我們更好地組織和管理這些非同步任務。